'abuimage.monkey by Belimoth

'TODO
'	bank all images somewhere behind the scenes so that identical images aren't loaded twice
'	automatically load new images if the graphics context has been set by mojo
'	texture atlases and 9-patch images
'	change handle positions to floats

Strict
Import mojo
Import os
Import skn3.xml



Public

Const FLIP_X:Int = 1			'TODO put these in documentation
Const FLIP_Y:Int = 2



Class AbuImage

Public
	'[read-only]
	Method Width:Int() Property Return _image.Width End
	Method Height:Int() Property Return _image.Height End
	
	'[read & write]
	Method HandleX:Int() Property Return _handleX End
	Method HandleY:Int() Property Return _handleY End	
	Method SetHandle:Void(x:Int, y:Int)
		_handleX = x
		_handleY = y
	End
	
	Method New(path:String, textureAtlas:AbuTextureAtlas = Null)		'textureAtlas not implemented yet
		Self._image = ImageBank.Find(path)
	End
	
	Method SetMidHandle:Void()
		'TODO
	End

	Method Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		'#If TARGET = "html5"
		
		'	Local color:Float[] = GetColor()
		'	SetColor(255, 255, 255)
		
		'	...
			
		'	SetColor(c[0], c[1], c[2])
			
		'#Else
		
			_image.Draw(x, y, rotation, scaleX, scaleY)
			
		'#End
	End
	
	Method DrawFlipped:Void(x:Int, y:Int, flip:Int = FLIP_X, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		'TODO simplify this
		_image.SetHandle(_handleX, _handleY)
		Local flippedX:Int = 1, flippedY:Int = 1
		If flip & FLIP_X
			flippedX = -1
			_image.SetHandle(Width - _handleX, _handleY)
		Endif
		If flip & FLIP_Y
			flippedY = -1
			_image.SetHandle(_handleX, Height - _handleY)
		Endif
		Draw(x, y, rotation, scaleX * flippedX, scaleY * flippedY)
	End
	
Private
	Field variations:StringMap<Image>		'not implemented
	Field _image:ImageResource
	Field _handleX:Int, _handleY:Int
	
	'Method AddVariation:Void(color:Color)		'not implemented, and palette parameter removed
		
	'End
End



Class AbuAnimationSheet Extends AbuImage

Public
	'[read-only]
	Method FrameWidth:Int() Property Return _frameWidth End
	Method FrameHeight:Int() Property Return _frameHeight End
	
	Method New(path:String, frameWidth:Int, frameHeight:Int, textureAtlas:AbuTextureAtlas = Null)		'textureAtlas not implemented yet
		Self._image = ImageBank.Find(path)
		Self._frameWidth = frameWidth
		Self._frameHeight = frameHeight
	End
	
	Method Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		'TODO should draw entire sheet as a single image
	End
	
	Method DrawFrame:Void(x:Int, y:Int, frame:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		If Not _image.loaded Then _image.Load()		'workaround so that LoadFrames is guaranteed to work
		If Not _framesLoaded Then LoadFrames()
		
		Local f:Frame = _frames[frame]
		_image.DrawRect(x, y, f.x, f.y, _frameWidth, _frameHeight, rotation, scaleX, scaleY)
	End
	
	Method DrawFrameFlipped:Void(x:Int, y:Int, frame:Int, flip:Int = FLIP_X, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		_image.SetHandle(_handleX, _handleY)
		Local flippedX:Int = 1, flippedY:Int = 1
		If flip & FLIP_X
			flippedX = -1
			_image.SetHandle(_frameWidth - _handleX, _handleY)
		Endif
		If flip & FLIP_Y
			flippedY = -1
			_image.SetHandle(_handleX, _frameHeight - _handleY)
		Endif
		DrawFrame(x, y, frame, rotation, scaleX * flippedX, scaleY * flippedY)
	End

Private
	Field _frameWidth:Int
	Field _frameHeight:Int
	Field _frames:Frame[]
	Field _framesLoaded:Bool
	
	Method LoadFrames:Void()
		Local colTemp:Int = _image.Width / _frameWidth
		Local rowTemp:Int = _image.Height / _frameHeight
		Local frameCount:Int = colTemp * rowTemp
		_frames = New Frame[frameCount]
		Local n:Int = 0
		For Local y:Int = 0 Until rowTemp
		For Local x:Int = 0 Until colTemp
			_frames[n] = New Frame(x * _frameWidth, y * _frameHeight)
			n += 1
		Next
		Next
	End
End



Function LoadImages:Void()
	For Local image := Eachin ImageResource.notYetLoaded
		image.Load()
	Next
End



Private

Class Frame		'mojo's Frame is private or I would just use that
	Field x:Int, y:Int
	
	Method New(x:Int, y:Int)
		Self.x = x
		Self.y = y
	End
End



'TODO
'	load @2x images when necessary
'	loading image sets by prefix


Class AbuTextureAtlas		'placeholder, find a good place to put this

End



Class ImageResource

Public
	'[read-only]
	Method loaded:Bool() Property Return _loaded End
	
	Method Width:Int() Property
		If Not _loaded Then Return 0
		Return _image.Width()
	End
	
	Method Height:Int() Property
		If Not _loaded Then Return 0
		Return _image.Height()
	End
	
	Method SetHandle:Void(x:Int, y:Int)
		_handleX = x
		_handleY = y
		If _loaded Then _image.SetHandle(x, y)
	End
	
	Method New(path:String)
		Self._path = path
		Self._loaded = False
		notYetLoaded.AddLast(Self)
	End
	
	Method Draw:Void(x:Int, y:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		If Not _loaded Then Load()
		DrawImage(_image, x, y, rotation, scaleX, scaleY)
	End
	
	Method DrawRect:Void(x:Int, y:Int, sourceX:Int, sourceY:Int, sourceWidth:Int, sourceHeight:Int, rotation:Float = 0, scaleX:Float = 1, scaleY:Float = 1)
		If Not _loaded Then Load()
		DrawImageRect(_image, x, y, sourceX, sourceY, sourceWidth, sourceHeight, rotation, scaleX, scaleY)
	End
	
	Method Load:Void()
		_image = LoadImage(_path)
		If _image = Null Then Error(_path + " not found.")
		_image.SetHandle(_handleX, _handleY)
		_loaded = True
		notYetLoaded.Remove(Self)
	End
	
Private
	Global notYetLoaded := New List<ImageResource>
	
	Field _path:String
	Field _loaded:Bool
	Field _image:Image
	Field _handleX:Int, _handleY:Int
End



Class AtlasResource Extends ImageResource
	Field dictionary := New AtlasDictionary()
	
	Method New(path:String)
		Self._path = path
		Self._loaded = False
		notYetLoaded.AddLast(Self)
	End
	
	Method Load:Void()
		_image = LoadImage(_path)
		_image.SetHandle(_handleX, _handleY)
		_loaded = True
		notYetLoaded.Remove(Self)
		dictionary.Load()
	End
End



Class AtlasDictionary
	Field fragments := New StringMap<AtlasFragment>

	Method Add:Void(fragment:AtlasFragment)
		fragments.Set(fragment._name, fragment)
	End
	
	Method Get:AtlasFragment(name:String)
		Return fragments.Get(name)
	End
	
	Method Load:Void()
		For Local fragment := Eachin fragments.Values()
			fragment.Load()
		Next
	End
End



Class AtlasFragment Extends ImageResource

Public
	Method New(atlas:AtlasResource, name:String, x:Int, y:Int, width:Int, height:Int)
		Self._atlas = atlas
		Self._name = name
		Self._sourceX = x
		Self._sourceY = y
		Self._sourceWidth = width
		Self._sourceHeight = height
		Self._loaded = false
	End
	
	Method Load:Void()
		If Not _atlas.loaded Then _atlas.Load()		'workaround, these functions call eachother in a circular fashion FIX THIS
		_image = _atlas._image.GrabImage(_sourceX, _sourceY, _sourceWidth, _sourceHeight)
		_image.SetHandle(_atlas._handleX, _atlas._handleY)
		_loaded = True
		notYetLoaded.Remove(Self)
	End
	
Private
	Field _atlas:AtlasResource
	Field _name:String
	Field _sourceX:Int
	Field _sourceY:Int
	Field _sourceWidth:Int
	Field _sourceHeight:Int
End



Class ImageBank

Public
	Function Find:ImageResource(path:String)
		If path.Contains("#")
			Local split:String[] = path.Split("#")
			Return Find(split[0], split[1])
		Else
			'TODO assumes that this is an image but it could be an atlas if it doesn't have an extension
			Local imageTemp:ImageResource = _map.Get(path)
			If imageTemp = Null Then imageTemp = Load(path)
			Return imageTemp
		Endif
	End
	
	Function Find:ImageResource(atlasPath:String, subTexture:String)
		Local stripped:String = StripExt(atlasPath)
		If stripped = atlasPath Then atlasPath += ".png"
		If _map.Get(atlasPath) = Null Then LoadAtlas(atlasPath)
		Local dictionary:AtlasDictionary = _dictionaries.Get(atlasPath)
		Return dictionary.Get(subTexture)
	End
	
	Function Load:ImageResource(path:String)
		Local image := New ImageResource(path)
		_map.Set(path, image)
		Return image
	End
	
	Function LoadAtlas:AtlasResource(path:String)		'atlas path should have no extension
		path = StripExt(path)
		Local document:XMLDoc = ParseXML( LoadString(path + ".xml") )
		Local imagePath:String = path + ".png"
		
		#Rem
		If document.Root.HasAttribute("imagePath")
			'TODO this disregards the directory?
			imagePath = document.Root.GetAttribute("imagePath")
		Else
			imagePath = path + ".png"
		Endif
		#End
		
		Local atlas := New AtlasResource(imagePath)
		Local atlasDictionary := New AtlasDictionary()
		atlas.dictionary = atlasDictionary
		
		For Local node:XMLNode = EachIn document.children
			Local nameTemp:String = node.GetAttribute("name")
			Local xTemp:Int = Int( node.GetAttribute("x") )
			Local yTemp:Int = Int( node.GetAttribute("y") )
			Local wTemp:Int = Int( node.GetAttribute("width") )
			Local hTemp:Int = Int( node.GetAttribute("height") )
			atlasDictionary.Add( New AtlasFragment(atlas, nameTemp, xTemp, yTemp, wTemp, hTemp) )
		Next
		
		_map.Set(imagePath, atlas)
		_dictionaries.Set(imagePath, atlasDictionary)
		Return atlas
	End
	
Private
	Global _map := New StringMap<ImageResource>
	Global _dictionaries := New StringMap<AtlasDictionary>
End

